/**
 * 
 */
package br.com.beyondclick.model.dao.jpa;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import br.com.beyondclick.model.dao.CompraDAO;
import br.com.beyondclick.model.entity.Cheque;
import br.com.beyondclick.model.entity.Compra;
import br.com.beyondclick.model.entity.CompraMaterial;
import br.com.beyondclick.model.entity.CompraPedidoMaterial;
import br.com.beyondclick.model.entity.Material;
import br.com.beyondclick.model.entity.PedidoMaterial;
import br.com.gentech.commons.model.dao.DAOException;
import br.com.gentech.commons.model.dao.jpa.JpaBaseDAO;



/**
 * @author acdesouza
 *
 */
public class JpaCompraDAO extends JpaBaseDAO<Compra, Long> implements CompraDAO{

	public JpaCompraDAO(EntityManager manager) {
		super(manager);
	}

	@Override
	public Class<Compra> getEntityClass() {
		return Compra.class;
	}
	
	/** 
	 * Executar {@link #atualizarQuantidadeCompradaEm(Compra)} antes de executar a grava��o.
	 * @see br.com.gentech.commons.model.dao.jpa.JpaBaseDAO#save(br.com.gentech.commons.model.entity.CrudBaseEntity)
	 */
	@Override
	public Compra save(Compra unsaved) {
		atualizarQuantidadeCompradaEm(unsaved);
		return super.save(unsaved);
	}

	/**
	 * FIXME Atulizar os valores comprados, retirando os que foram deletados.
	 * @see br.com.gentech.commons.model.dao.jpa.JpaBaseDAO#remove(java.lang.Object)
	 */
	@Override
	public void remove(Long id) {
		super.remove(id);
	}

	/**
	 * Atualizar a quantidade comprada dos {@link PedidoMaterial} envolvidos na {@link Compra}.
	 * @param unsaved
	 */
	private void atualizarQuantidadeCompradaEm(Compra unsaved){
		try{
			List<CompraPedidoMaterial> lstPedidoMaterial = new ArrayList<CompraPedidoMaterial>();
			for (CompraMaterial compraMaterial : unsaved.getCompraMateriais()) {
				lstPedidoMaterial.addAll(compraMaterial.getCompraPedidoMateriais());
			}
			for (CompraPedidoMaterial compraPedidoMaterial : lstPedidoMaterial) {
				manager.merge(compraPedidoMaterial.getPedidoMaterial());
			}
		}catch(NullPointerException e){
			throw new DAOException("Lista de PedidoMaterial est� nula.", e);
		}
	}

	/**
	 * 
select
	new map( 
		m as material
		, sum(pm.quantidade) - sum(cpm.quantidade) as qtdPendente
	)
from
	CompraPedidoMaterial cpm
	inner join cpm.pedidoMaterial pm
	inner join pm.material m
group by
	cpm.pedidoMaterial
	, m.id
	, m.nome
	, m.fornecedor
	, m.descricao
	, m.categoria
	, m.quantidade
having
	sum(pm.quantidade) > sum(cpm.quantidade)
	 */
	public Map<Material, Long> findMateriaisPendentesCompra() {

		Map<Material, Long> materialPedido = new HashMap<Material, Long>();
		Map<Material, Long> materialComprado = new HashMap<Material, Long>();
		Map<Material, Long> materialPendente = new HashMap<Material, Long>();

		List<PedidoMaterial> pedidoMateriaisPendentes = findPedidoMaterialPendetes();
		
		long quantidadePendente = 0;
		
		Material material;
		for (PedidoMaterial pedidoMaterial : pedidoMateriaisPendentes) {
			material = pedidoMaterial.getMaterial();

			//Totalizar a quantidade de materiais pedidos.
			long quantidadePedida = pedidoMaterial.getQuantidade();
			if( materialPedido.containsKey(material) ){
				quantidadePedida += materialPedido.get(material);
			}
			materialPedido.put(material, quantidadePedida);

			//Totalizar a quantidade de materiais comprados.
			long quantidadeComprada = 0;
			final List<CompraPedidoMaterial> compraPedidoMateriais = pedidoMaterial.getCompraPedidoMateriais();
			for (CompraPedidoMaterial compraPedidoMaterial : compraPedidoMateriais) {
				quantidadeComprada +=  compraPedidoMaterial.getQuantidade();
			}
			if( materialComprado.containsKey(material) ){
				quantidadeComprada += materialComprado.get(material);
			}else{
				materialComprado.put(material, quantidadeComprada);
			}

			//Calcular a quantidade de materiais pendentes de compra.
			quantidadePendente = quantidadePedida - quantidadeComprada;
			materialPendente.put(material, quantidadePendente);

			//Reiniciar as vari�veis.
			quantidadePedida = 0;
			quantidadePendente = 0;
			quantidadeComprada = 0;
		}

		return materialPendente;
	}

	/**
	 *
select
	distinct pm
from
	PedidoMaterial pm
		left join fetch pm.compraPedidoMateriais 
where
	pm not in(
		select 
			cpm_naocomprado.pedidoMaterial
		from 
			CompraPedidoMaterial cpm_naocomprado
	)
or	pm.quantidade > (
		select
			sum(cpm_parcial.quantidade)
		from
			CompraPedidoMaterial cpm_parcial
		where
			cpm_parcial.pedidoMaterial = pm
	)
order by
	pm.pedido.data
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<PedidoMaterial> findPedidoMaterialPendetes() {
		StringBuilder hql = new StringBuilder();
		hql.append("select ");
		hql.append("	pm ");
		hql.append("from PedidoMaterial pm ");
		hql.append("	left join fetch pm.compraPedidoMateriais ");
		hql.append("where ");
		hql.append("	pm not in( ");
		hql.append("		select cpm_naocomprado.pedidoMaterial ");
		hql.append("		from CompraPedidoMaterial cpm_naocomprado ");
		hql.append("	) ");
		hql.append("or	pm.quantidade > ( ");
		hql.append("		select sum(cpm_parcial.quantidade) ");
		hql.append("		from CompraPedidoMaterial cpm_parcial ");
		hql.append("		where cpm_parcial.pedidoMaterial = pm ");
		hql.append("	) ");
		hql.append("order by ");
		hql.append("	pm.pedido.data ");
		
		Query query = manager.createQuery(hql.toString());
		Set<PedidoMaterial> setPedidoMaterial = new HashSet<PedidoMaterial>(query.getResultList());
		List<PedidoMaterial> pedidoMaterial = new ArrayList<PedidoMaterial>(setPedidoMaterial);

		return pedidoMaterial;
	}

	/**
	 * FIXME Falta mostrar o quanto est� sobrando, ao inv�s do quanto foi comprado.
select
    new map(
		cm
		, (cm.quantidade - sum(cpm.quantidade)) as emEstoque
	)
from
	CompraMaterial cm
	inner join cm.compraPedidoMateriais cpm
where
	cm.	quantidade > (
		select
			sum(cpmUsados.quantidade)
		from
			CompraPedidoMaterial cpmUsados
		where
			cpmUsados.compraMaterial = cm
	)
group by
	cm.id
	, cpm.quantidade
	, cm.quantidade
	, cm.material
	, cm.preco
	, cm.compra
	 * @return
	 */
	public Map<CompraMaterial, Long> findCompraMaterialEmEstoque() {

		StringBuilder hql = new StringBuilder();
		hql.append("select ");
		hql.append("	new map( ");
		hql.append("		cm as compraMaterial ");
		hql.append("		, (cm.quantidade - sum(cpm.quantidade)) as emEstoque ");
		hql.append("	) ");
		hql.append("from ");
		hql.append("	CompraMaterial cm ");
		hql.append("	inner join cm.compraPedidoMateriais cpm ");
		hql.append("where ");
		hql.append("	cm.	quantidade > ( ");
		hql.append("		select ");
		hql.append("			sum(cpmUsados.quantidade) ");
		hql.append("		from ");
		hql.append("			CompraPedidoMaterial cpmUsados ");
		hql.append("		where ");
		hql.append("			cpmUsados.compraMaterial = cm ");
		hql.append("	) ");
		hql.append("group by ");
		hql.append("	cm.id ");
		hql.append("	, cpm.quantidade ");
		hql.append("	, cm.quantidade ");
		hql.append("	, cm.material ");
		hql.append("	, cm.preco ");
		hql.append("	, cm.compra ");

		Query query = manager.createQuery(hql.toString());

		Map<CompraMaterial, Long> compraMaterialEmEstoque = new HashMap<CompraMaterial, Long>();
		List<Map> resultado = query.getResultList();
		for (Map object : resultado) {
			compraMaterialEmEstoque.put(
					(CompraMaterial)object.get("compraMaterial")
					, (Long)object.get("emEstoque") );
		}
		 
		
		return compraMaterialEmEstoque;
	}

	/**
	 * @see br.com.beyondclick.model.dao.CompraDAO#find(br.com.beyondclick.model.entity.Cheque)
	 */
	@SuppressWarnings("unchecked")
	public List<CompraPedidoMaterial> find(Cheque cheque) {
		List<CompraPedidoMaterial> compraPedidoMaterial = new ArrayList<CompraPedidoMaterial>();
		
		StringBuilder hql = new StringBuilder();
		hql.append("from Compra c ");
		hql.append("left join fetch c.compraMateriais cm ");
		hql.append("where c.cheque = :cheque ");
		hql.append("order by c.data ");
		
		Query query = manager.createQuery(hql.toString());
		query.setParameter("cheque", cheque);
		
		Set<Compra> compras = new LinkedHashSet<Compra>(query.getResultList());
		
		for (Compra compra : compras) {
			for (CompraMaterial compraMaterial : compra.getCompraMateriais()) {
				hql = new StringBuilder();
				hql.append("from CompraMaterial cm ");
				hql.append("left join fetch cm.compraPedidoMateriais cpm ");
				hql.append("left join fetch cpm.pedidoMaterial pm ");
				hql.append("left join fetch pm.pedido p ");
				hql.append("left join fetch p.orgao ");
				hql.append("left join fetch cpm.compraMaterial cm ");
				hql.append("where cm = :compraMaterial ");
				hql.append("order by p.orgao, pm.material ");
				
				query = manager.createQuery(hql.toString());
				query.setParameter("compraMaterial", compraMaterial);
				List<CompraMaterial> cm = query.getResultList();
				
				for (CompraPedidoMaterial pedidoMaterial : cm.get(0).getCompraPedidoMateriais()) {
					pedidoMaterial.getPedidoMaterial().setCompraPedidoMateriais(cm.get(0).getCompraPedidoMateriais());
					pedidoMaterial.getCompraMaterial().setCompra(compra);
					compraPedidoMaterial.add(pedidoMaterial);
				}
			}
		}
		Collections.sort(compraPedidoMaterial);
		return compraPedidoMaterial;
	}

	/**
	 * @see br.com.beyondclick.model.dao.CompraDAO#findCheque(java.util.Date, java.util.Date)
	 */
	@SuppressWarnings("unchecked")
	public List<Cheque> findCheque(Date inicio, Date fim) {
		StringBuilder hql = new StringBuilder();
		hql.append("select c.cheque ");
		hql.append("from Compra c ");
		hql.append("where c.data between :inicio and :fim ");
		hql.append("order by c.cheque.numero ");
		
		Query query = manager.createQuery(hql.toString());
		query.setParameter("inicio", inicio);
		query.setParameter("fim", fim);
		
		Set<Cheque> cheques = new LinkedHashSet<Cheque>(query.getResultList()); 
		return new ArrayList<Cheque>(cheques);
	}

	@SuppressWarnings("unchecked")
	public List<Compra> find(Date inicio, Date fim) {
		StringBuilder hql = new StringBuilder();
		hql.append("select c ");
		hql.append("from Compra c ");
		hql.append("where c.data between :inicio and :fim ");
		
		Query query = manager.createQuery(hql.toString());
		query.setParameter("inicio", inicio);
		query.setParameter("fim", fim);
		
		Set<Compra> compras = new LinkedHashSet<Compra>(query.getResultList()); 
		return new ArrayList<Compra>(compras);
	}
}